package com.yanqu.road.server.logic.smallgame.stage;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.yanqu.road.entity.config.smallgame.SmallGameConfig;
import com.yanqu.road.entity.config.smallgame.SmallGameType;
import com.yanqu.road.entity.enums.eLogMoneyType;
import com.yanqu.road.pb.smallgame.SmallGameProto;
import com.yanqu.road.server.logic.smallgame.SmallGame;
import com.yanqu.road.server.protocol.GameErrorCode;
import com.yanqu.road.utils.string.StringUtils;

import java.util.*;

@SmallGame(type = SmallGameType.WaterSort, desc = "调饮大师")
public class WaterSortStage implements SmallGameStageInterface {
    private List<WaterBottle> waterBottles;

    private List<List<Integer>> colorsConfig;

    private Map<String, Integer> dfsVisit;
    private int pourAmout = 0;
    private int deepMax = 50;
    private int answerDeep = 0;
    private List<List<Integer>> answerList;
    private int colorNum = 0;
    private int emptyNum = 0;
    private int deepNum = 0;

    public WaterSortStage(int colorNum, int deepNum, int emptyNum, int deepMax) {
        this.deepMax = deepMax;
        this.colorNum = colorNum;
        this.emptyNum = emptyNum;
        this.deepNum = deepNum;
        this.pourAmout = 0;
        this.dfsVisit = new HashMap<>();
        this.waterBottles = new ArrayList<>();
        this.answerList = new ArrayList<>();
        this.colorsConfig = new ArrayList<>();
    }

    public WaterSortStage() {
        this.dfsVisit = new HashMap<>();
        this.waterBottles = new ArrayList<>();
        this.answerList = new ArrayList<>();
        this.colorsConfig = new ArrayList<>();
    }

    public boolean randomInit() {
        if (emptyNum < 2) return false;
        List<Integer> randomColor = new ArrayList<>(colorNum * deepNum);
        for (int color = 1; color <= colorNum; color++) {
            WaterBottle waterBottle = new WaterBottle(deepNum);
            waterBottles.add(waterBottle);
            for (int i = 0; i < deepNum; i++) {
                randomColor.add(color);
            }
        }

        //颜色打乱
        Collections.shuffle(randomColor);
        int amount = 0;
        for (WaterBottle bottle : waterBottles) {
            for (int i = 0; i < deepNum; i++) {
                bottle.addColor(randomColor.get(i + amount * deepNum));
            }
            colorsConfig.add(bottle.getColors());
            amount++;
        }

        //加入空瓶子
        for (int i = 0; i < emptyNum; i++) {
            WaterBottle waterBottle = new WaterBottle(deepNum);
            waterBottles.add(waterBottle);
        }

        return true;
    }

    public void resetBottle() {
        this.dfsVisit.clear();
        this.waterBottles.clear();
        this.pourAmout = 0;
        this.answerList.clear();
        this.answerDeep = 0;
        for (List<Integer> colors : this.colorsConfig) {
            WaterBottle waterBottle = new WaterBottle(deepNum);
            waterBottle.addInitColor(colors);
            waterBottles.add(waterBottle);
        }

        //加入空瓶子
        for (int i = 0; i < emptyNum; i++) {
            WaterBottle waterBottle = new WaterBottle(deepNum);
            waterBottles.add(waterBottle);
        }
    }

    public boolean isEnd() {
        for (WaterBottle bottle : waterBottles) {
            if (!bottle.isOneColor()) {
                return false;
            }
            if (!bottle.isEmpty() && !bottle.isFull()) return false;
        }
        return true;
    }

    boolean dfsCanPour(int i, int j) {
        if (i == j) return false;
        if (i < 0 || i >= waterBottles.size() || j < 0 || j >= waterBottles.size()) return false;
        return waterBottles.get(j).dfsCanPourIn(waterBottles.get(i));
    }

    int dfsPour(int i, int j) {
        int x = 0;
        if (dfsCanPour(i, j)) {
            x = waterBottles.get(j).pourIn(waterBottles.get(i));
        }
        return x;
    }

    boolean pour(int i, int j) {
        if (i == j) return false;
        if (i < 0 || i >= waterBottles.size() || j < 0 || j >= waterBottles.size()) return false;
        if (!waterBottles.get(j).canPourIn(waterBottles.get(i))) {
            return false;
        }

        waterBottles.get(j).pourIn(waterBottles.get(i));
        return true;
    }

    void pourBack(int i, int j, int num) {
        waterBottles.get(i).pourBack(waterBottles.get(j), num);
    }

    private boolean dfsSolove(int deepAmount) {
        String key = getBottlesKey();
        if (dfsVisit.containsKey(getBottlesKey())) return false;
        dfsVisit.put(key, 1);
        if (isEnd() || deepAmount >= deepMax) {
            this.answerDeep = deepAmount;
            return true;
        }

        for (int i = 0; i < waterBottles.size(); i++) {
            for (int j = 0; j < waterBottles.size(); j++) {
                if (!dfsCanPour(i, j)) continue;
                pourAmout++;
                int pourNum = dfsPour(i, j);
                if (dfsSolove(deepAmount + 1)) {
                    //System.out.printf("deep=%d from %d to %d%n", deepAmount + 1, i + 1, j + 1);
                    List<Integer> step = new ArrayList<>();
                    step.add(i + 1);
                    step.add(j + 1);
                    answerList.add(step);
                    return true;
                }
                pourBack(j, i, pourNum);
            }
        }

        return false;
    }

    public boolean solve() {
        if (!this.dfsSolove(0))
            return false;
        Collections.reverse(this.answerList);
        this.dfsVisit.clear();
        return true;
    }

    String getBottlesKey() {
        String key = "";
        for (WaterBottle bottle : waterBottles) {
            key += bottle.toString() + "-";
        }
        return key;
    }

    public List<List<Integer>> getAnswerList() {
        return this.answerList;
    }

    public int getAnswerDeep() {
        return answerDeep;
    }

    public List<List<Integer>> getColorsConfig() {
        return colorsConfig;
    }

    public int getColorNum() {
        return colorNum;
    }

    public int getEmptyNum() {
        return emptyNum;
    }

    public int getDeepNum() {
        return deepNum;
    }

    public boolean isHaveOneFullColor() {
        for (WaterBottle bottle : waterBottles) {
            if (bottle.isOneFullColor())
                return true;
        }
        return false;
    }

    public String getParam() {
        String param = this.colorNum + "|" + this.emptyNum + "|" + this.deepNum;
        return param;
    }

    @Override
    public int verifyOperation(SmallGameProto.SmallGameChallengeSuccessReqMsg reqMsg) {
        if (!reqMsg.hasWaterSortOp()) {
            return GameErrorCode.E_SMALLGAME_WATER_SORT_FAILURE;
        }
        for (SmallGameProto.WaterSortOperationInfo operationInfo : reqMsg.getWaterSortOp().getOperationListList()) {
            int i = operationInfo.getFromNum() - 1;
            int j = operationInfo.getToNum() - 1;
            if (!pour(i, j)) {
                return GameErrorCode.E_SMALLGAME_WATER_SORT_FAILURE;
            }
        }
        if (!isEnd()) {
            return GameErrorCode.E_SMALLGAME_WATER_SORT_FAILURE;
        }
        return 0;
    }

    @Override
    public eLogMoneyType getLogMoneyType() {
        return eLogMoneyType.SmallGameChallengeWaterSort;
    }

    @Override
    public SmallGameStageInterface initByConfig(SmallGameConfig config) {
        if (null == config) {
            return null;
        }

        if (StringUtils.isNullOrEmpty(config.getStageData())) {
            return null;
        }

        List<Integer> params = StringUtils.stringToIntegerList(config.getParam(), "\\|");
        if (params.size() < 3) {
            return null;
        }
        this.colorNum = params.get(0);
        this.emptyNum = params.get(1);
        this.deepNum = params.get(2);

        this.waterBottles.clear();
        List<List<Integer>> colorsList = JSON.parseObject(config.getStageData(), new TypeReference<List<List<Integer>>>() {
        });

        for (List<Integer> colors : colorsList) {
            WaterBottle waterBottle = new WaterBottle(deepNum);
            waterBottle.addInitColor(colors);
            waterBottles.add(waterBottle);
        }

        //加入空瓶子
        for (int i = 0; i < emptyNum; i++) {
            WaterBottle waterBottle = new WaterBottle(deepNum);
            waterBottles.add(waterBottle);
        }

        return this;
    }
}
